9.App Startup简介
App Startup库提供了一种直接,高效的方法,可以在应用程序启动时初始化组件。第三方库开发人员和应用程序开发人员都可以使用App Startup来简化启动顺序并显式设置初始化顺序。
App Startup允许共享单个内容提供者来进行组件的初始化,而无需为需要初始化的每个组件定义单独的内容提供者。这可以大大缩短应用程序的启动时间。
这里提到了内容提供者,会让人摸不到头脑,内容提供者会应用初始化有什么关系?
背景
我们在使用一些第三方库时一般都需要在Application.onCreate()方法中进行一些init或者install等初始化操作:
class StApplication : Application() {
override fun onCreate() {
Fresco.initialize(this)
LitePalApplication.initialize(this)
Adxxx.init(this)
....
}
}
但是在之前LeakCanary源码分析的文章中曾经介绍过LeakCanary是如何做到不用任何一行Java代码就能实现监测内存泄露功能的。 这里我们回顾一下:
How does LeakCanary get installed by only adding a dependency?¶
On Android, content providers are created after the Application instance is created but before Application.onCreate() is called. The leakcanary-object-watcher-android
artifact has a non exported ContentProvider defined in its AndroidManifest.xml
file. When that ContentProvider is installed, it adds activity and fragment lifecycle listeners to the application.
对于Application的onCreate()方法,官方文档中是这样说的:
Called when the application is starting, before any activity, service, or receiver objects(excluding content providers)have been created.
看到这里,大呼内行...这个实现太巧妙了。尽然是通过在AndroidManifest.xml中注册一个内容提供者,这个内容提供者的创建会在Application.onCreate()之前,在它创建的时候去做install的操作。
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
/**
* Content providers are loaded before the application class is created. [AppWatcherInstaller] is
* used to install [leakcanary.AppWatcher] on application start.
*/
internal sealed class AppWatcherInstaller : ContentProvider() {
/**
* [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
*/
internal class MainProcess : AppWatcherInstaller()
/**
* When using the `leakcanary-android-process` artifact instead of `leakcanary-android`,
* [LeakCanaryProcess] automatically sets up the LeakCanary code
*/
internal class LeakCanaryProcess : AppWatcherInstaller()
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
// 调用install方法进行初始化
AppWatcher.manualInstall(application)
return true
}
override fun query(
uri: Uri,
strings: Array<String>?,
s: String?,
strings1: Array<String>?,
s1: String?
): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
return null
}
override fun insert(
uri: Uri,
contentValues: ContentValues?
): Uri? {
return null
}
override fun delete(
uri: Uri,
s: String?,
strings: Array<String>?
): Int {
return 0
}
override fun update(
uri: Uri,
contentValues: ContentValues?,
s: String?,
strings: Array<String>?
): Int {
return 0
}
}
通过ContentProvider进行初始化的优缺点
优点:
大大简化了我们的接入成本,无须任何Java代码即可使用。
缺点:
ContentProvider会增加许多额外的耗时。
应用和库往往需要在Application.onCreate()之前初始化组件,如: WorkManager、ProcessLifecycleObserver、FirebaseApp 等。这往往通过使用ContentProvider来实现不同依赖的初始化。通过 App Startup,您无需为每个组件单独定义ContentProvider进行初始化,而可以定义多个 Initializer去共享相同的ContentProvider。每减少一个ContentProvider通常会有约2ms的收益,这可以显著提高应用启动速度。
毕竟ContentProvider是Android四大组件之一,这个组件相对来说是比较重量级的。也就是说,本来我的初始化操作可能是一个非常轻量级的操作,依赖于ContentProvider之后就变成了一个重量级的操作了。假如一个应用使用了很多第三方库,而每个库都会用一个ContentProvider进行初始化,那就会大大影响应用的启动时间。
关于ContentProvider的耗时,Google官方也有给出一个测试结果:
在一台搭载Android 10系统的Pixel2手机上测试的情况。可以看到,一个空的ContentProvider大约会占用2ms的耗时,随着ContentProvider的增加,耗时也会跟着一起增加。如果你的应用程序中使用了50个ContentProvider,那么将会占用接近20ms的耗时。
注意这还只是空ContentProvider的耗时,并没有算上你在ContentProvider中执行逻辑的耗时。这个测试结果告诉我们,虽然刚才所介绍的使用ContentProvider来进行初始化的设计方式很巧妙,但是如果每个第三方库都自己创建了一个ContentProvider,那么最终我们App的启动速度就会受到比较大的影响。 有没有办法解决这个问题呢?
所以得出结论,当集成的库使用的ContentProvider达到一定个数之后,确实能减少耗时,但是减少的不多,比如这里我们是10个ContentProvider集成App Startup后能减少的耗时在10ms左右,一般一个项目集成了五十个使用ContentProvider的库,耗时减少应该能在20ms之内。
所以App Startup解决的就是这个耗时时间,虽然不多,但是也确实有减少耗时的功能。
Android会以不确定的顺序初始化内容提供者
App Startup原理
App Startup提供了一种更高效的方法,可在应用程序启动时初始化组件并显式定义其依赖关系它可以将所有用于初始化的ContentProvider合并成一个,从而使App的启动速度变得更快。而且它还可以指定不同组件初始化的依赖关系。
两点特性:
- 可以共享单个Contentprovider。
- 可以明确地设置初始化顺序。
具体来讲,App Startup内部也创建了一个ContentProvider,并提供了一套用于初始化的标准。然后对于其他第三方库来说,你们就不需要再自己创建ContentProvider了,都按我的这套标准进行实现就行了,我可以保证你们的库在App启动之前都成功进行初始化。 当App启动的时候会自动执行App Startup库中内置的一个名为 InitializationProvider 的 ContentProvider,该 ContentProvider 在合并后的AndroidManifest.xml文件中查找条目来发现Initializer,然后逐个调用它们的create()方法来进行初始化操作。完成上一阶段之后,加载组件之前会先加载该组件的所有依赖项。因此,可以确保组件的所有依赖项都已完成初始化后才对其进行初始化。
使用
1.添加依赖
想要在库或者app中使用Jetpack Startup组件,需要在Gradle文件中添加一下代码:
dependencies {
implementation "androidx.startup:startup-runtime:1.0.0"
}
2.定义组件初始化器
想要使用App Startup在应用启动时自动初始化组件,你必须要为每一个要初始化的组件定义一个实现Initializer
Initialize接口是Startup封装的组件接口,用于指定组件的初始化逻辑和初始化顺序。
这个接口里面有两个重要的方法:
create():初始化操作
包含所有组件初始化所需要的操作,并且返回一个该组件T的实例
dependencies(): 依赖关系
返回该组件initializer所需要依赖的一个包含其他Initializer
对象的list集合。可以在该方法中去控制应用启动时initializers的执行顺序。
假设你的应用需要依赖WorkManager,并在启动时进行初始化,需要定义一个WorkManagerInitializer类实现Initializer
// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
override fun create(context: Context): WorkManager {
val configuration = Configuration.Builder().build()
WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// No dependencies on other libraries.
return emptyList()
}
}
dependencies()方法中返回了一个空的list,因为WorkManager不需要依赖其他任何库。
假设你需要使用另外一个叫做ExampleLogger的库,该库依赖于WorkManager。这种情况下你必须保证应用程序启动后先初始化WorkManager。这里定义一个ExampleLoggerInitializer类实现Initializer
// Initializes ExampleLogger.
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
override fun create(context: Context): ExampleLogger {
// WorkManager.getInstance() is non-null only after
// WorkManager is initialized.
return ExampleLogger(WorkManager.getInstance(context))
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// Defines a dependency on WorkManagerInitializer so it can be
// initialized after WorkManager is initialized.
return listOf(WorkManagerInitializer::class.java)
}
}
这里因为在dependencies()方法中包含了WorkManagerInitializer,App Startup会在ExampleLogger初始化前先初始化WorkManager。
3.配置manifest文件
App Startup包含一个称为InitializationProvider的特殊内容提供程序,它可用于发现和调用组件初始化器。 App Startup通过首先检查manifest文件下的InitializationProvider条目下的
这就意味着,为了使组件初始化器可被App Startup发现,必须满足以下条件之一:
- 组件初始化器在InitializationProvider清单条目下具有一个对应的
条目。 - 组件初始化器已在另一个可发现的初始化器中的dependencies()方法中列出。
再次考虑带有WorkManagerInitializer和ExampleLoggerInitializer的示例。为确保App Startup可以发现这些初始化器,请将以下内容添加到清单文件中:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- This entry makes ExampleLoggerInitializer discoverable. -->
<meta-data android:name="com.example.ExampleLoggerInitializer"
android:value="androidx.startup" />
</provider>
要点如下:
- 1、组件名必须是
androidx.startup.InitializationProvider
; - 2、需要声明
android:exported="false"
,以限制其他应用访问此组件; - 3、要求
android:authorities
在整个手机唯一,通常使用${applicationId}作为前缀; - 4、需要声明
tools:node="merge"
,确保manifest merger tool
能够正确解析冲突的节点;用来合并所有申明了InitializationProvider
的ContentProvider
。 - 5、meta-data
name
为组件的 Initializer实现类全限定名,value
为androidx.startup
。
您不需要为WorkManagerInitializer添加
运行lint工具检查
为了检查你是否正确使用了App Startup组件,可以运行./gradlew :app:lintDebug命令来校验。
手动初始化
通常当你使用App Startup时,InitializationProvider使用一个名为AppInitializer的组件来自动发现并运行你的initializers组件。然而,你也可以使用AppInitializer来手动启动这些初始化组件,这叫做延迟初始化,可以帮助你减少启动消耗。 如果你要手动初始化,你需要首先关闭自动初始化功能。
关闭单个组件的自动初始化
为了关闭单个组件的自动初始化,需要在
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
</provider>
假设我们在项目中引入的某个第三方依赖库自身使用到了Startup进行自动初始化,我们希望将之改为延迟初始化的方式,但我们无法直接修改第三方依赖库的AndroidManifest文件,此时就可以通过AndroidManifest的合并规则来移除指定的Initializer。
假设第三方依赖库的Initializer的包名路径是 xxx.xxx.InitializerImpl
,在主项目工程的AndroidManifes文件中主动对其进行声明,并添加 tools:node="remove"
语句要求在合并AndroidManifest文件时移除自身,这样Startup就不会自动初始化InitializerImpl了。
如果想要禁用所有组件的自动初始化功能,同时又需要使用manifet文件合并工具的合并规则,就需要移除manifet文件中InitializationProvider中的整个节点:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
可以手动使用AppInitialize来进行组件和它所依赖组件的初始化,例如:
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer::class.java)
App Startup同样会初始化WorkManager,因为WorkManager是ExampleLogger的一个依赖。
App Startup中会缓存初始化后的结果,重复调用initializeComponent()方法不会导致重复初始化。
源码分析
/**
* The {@link ContentProvider} which discovers {@link Initializer}s in an application and
* initializes them before {@link Application#onCreate()}.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class InitializationProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context context = getContext();
if (context != null) {
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
@Nullable
@Override
public Cursor query(
@NonNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int delete(
@NonNull Uri uri,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int update(
@NonNull Uri uri,
@Nullable ContentValues values,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed.");
}
}
代码很简单,里面调用AppInitializer.discoverAndInitialize()方法:
/**
* An {@link AppInitializer} can be used to initialize all discovered [ComponentInitializer]s.
* <br/>
* The discovery mechanism is via `<meta-data>` entries in the merged `AndroidManifest.xml`.
*/
@SuppressWarnings("WeakerAccess")
public final class AppInitializer {
// Tracing
private static final String SECTION_NAME = "Startup";
/**
* The {@link AppInitializer} instance.
*/
private static volatile AppInitializer sInstance;
/**
* Guards app initialization.
*/
private static final Object sLock = new Object();
@NonNull
final Map<Class<?>, Object> mInitialized;
@NonNull
final Set<Class<? extends Initializer<?>>> mDiscovered;
@NonNull
final Context mContext;
/**
* Creates an instance of {@link AppInitializer}
*
* @param context The application context
*/
AppInitializer(@NonNull Context context) {
mContext = context.getApplicationContext();
mDiscovered = new HashSet<>();
mInitialized = new HashMap<>();
}
/**
* @param context The Application {@link Context}
* @return The instance of {@link AppInitializer} after initialization.
*/
@NonNull
@SuppressWarnings("UnusedReturnValue")
public static AppInitializer getInstance(@NonNull Context context) {
if (sInstance == null) {
synchronized (sLock) {
if (sInstance == null) {
sInstance = new AppInitializer(context);
}
}
}
return sInstance;
}
/**
* Initializes a {@link Initializer} class type.
*
* @param component The {@link Class} of {@link Initializer} to initialize.
* @param <T> The instance type being initialized
* @return The initialized instance
*/
@NonNull
@SuppressWarnings("unused")
public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
return doInitialize(component, new HashSet<Class<?>>());
}
/**
* Returns <code>true</code> if the {@link Initializer} was eagerly initialized..
*
* @param component The {@link Initializer} class to check
* @return <code>true</code> if the {@link Initializer} was eagerly initialized.
*/
public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) {
// If discoverAndInitialize() was never called, then nothing was eagerly initialized.
return mDiscovered.contains(component);
}
@NonNull
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
<T> T doInitialize(
@NonNull Class<? extends Initializer<?>> component,
@NonNull Set<Class<?>> initializing) {
synchronized (sLock) {
boolean isTracingEnabled = Trace.isEnabled();
try {
if (isTracingEnabled) {
// Use the simpleName here because section names would get too big otherwise.
Trace.beginSection(component.getSimpleName());
}
if (initializing.contains(component)) {
String message = String.format(
"Cannot initialize %s. Cycle detected.", component.getName()
);
throw new IllegalStateException(message);
}
Object result;
// 先检查是否已经初始化过
if (!mInitialized.containsKey(component)) {
initializing.add(component);
try {
// 先反射检查denpendencies()方法
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
List<Class<? extends Initializer<?>>> dependencies =
initializer.dependencies();
if (!dependencies.isEmpty()) {
for (Class<? extends Initializer<?>> clazz : dependencies) {
if (!mInitialized.containsKey(clazz)) {
doInitialize(clazz, initializing);
}
}
}
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initializing %s", component.getName()));
}
// 调用create()方法
result = initializer.create(mContext);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initialized %s", component.getName()));
}
initializing.remove(component);
// 缓存已经初始化过的组件
mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
} else {
result = mInitialized.get(component);
}
return (T) result;
} finally {
Trace.endSection();
}
}
}
@SuppressWarnings("unchecked")
void discoverAndInitialize() {
try {
Trace.beginSection(SECTION_NAME);
ComponentName provider = new ComponentName(mContext.getPackageName(),
InitializationProvider.class.getName());
ProviderInfo providerInfo = mContext.getPackageManager()
.getProviderInfo(provider, GET_META_DATA);
Bundle metadata = providerInfo.metaData;
String startup = mContext.getString(R.string.androidx_startup);
if (metadata != null) {
// 遍历metadata节点
Set<Class<?>> initializing = new HashSet<>();
Set<String> keys = metadata.keySet();
for (String key : keys) {
String value = metadata.getString(key, null);
// 检查value为androidx.startup的值
if (startup.equals(value)) {
// 反射
Class<?> clazz = Class.forName(key);
if (Initializer.class.isAssignableFrom(clazz)) {
Class<? extends Initializer<?>> component =
(Class<? extends Initializer<?>>) clazz;
mDiscovered.add(component);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Discovered %s", key));
}
doInitialize(component, initializing);
}
}
}
}
} catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
throw new StartupException(exception);
} finally {
Trace.endSection();
}
}
}
遗憾
优点:使用 App Startup 框架,可以简化启动序列并显式设置初始化依赖顺序,在简单、高效这方面,App Startup 基本满足需求。
不足:App Startup 框架的不足也是因为它太简单了,提供的特性太过简单,往往并不能完美契合商业化需求。例如以下特性 App Startup 就无法满足:
异步处理呢?虽然我们可以在create()
方法中手动创建子线程进行异步任务,但一个异步任务依赖另一个异步任务又该如何处理呢?多个异步任务完成之后,统一逻辑处理又在哪里呢?依赖任务完成后的回调又在哪里?亦或者是依赖任务完成后的通知?
- InitializationProvider的onCreate()方法是在主线程被调用的,导致每个Initializer默认就都是运行在主线程,这对于某些初始化时间过长,需要运行在子线程的组件来说就不太适用了。且Initializer的
create(context: Context)
方法的本意是完成组件的初始化并返回初始化的结果值,如果在此处通过主动new Thread来运行耗时组件的初始化,那么我们就无法返回有意义的结果值,间接导致后续也无法通过 AppInitializer获取到缓存的初始化结果值。 对于已经使用 ContentProvider 完成初始化逻辑的第三方依赖库,我们一般也无法直接修改其初始化逻辑(除非 clone 该项目导到本地直接修改源码),所以在初始阶段 Startup 的意义主要在于统一项目本地组件的初始化入口,需要等到 Startup 被大多数开发者接受并使用后,才更加具有性能优势
缺乏异步等待:同步等待指的是在当前线程先初始化所依赖的组件,再初始化当前组件,App Startup 是支持的,但是异步等待就不支持了。举个例子,如果某组件的初始化需要依赖于其它耗时组件(初始化时间过长,需要运行在子线程)的结果值,此时Startup一样不适用。
- 缺乏依赖回调:当前组件所依赖的组件初始化完成后,未发出回调。
如果你的项目都是同步初始化的话,并且使用到了多个ContentProvider
,App Startup
可能有一定的优化空间,毕竟统一到了一个ContentProvider
中,同时支持了简单的顺序依赖。
值得一提的是,App Startup
中只提供了使用反射来获取初始化的组件实例,这对于一些没有过多依赖的初始化项目来说,盲目使用App Startup
来优化是否会对启动速度进一步造成影响呢?
所以就有了下面这个库,App Startup的进阶版Android Startup。
Why I abandoned the Jetpack App Startup?
- 邮箱 :[email protected]
- Good Luck!